/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */


package org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance;

import net.bytebuddy.description.method.MethodDescription;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.dynamic.DynamicType;
import net.bytebuddy.implementation.FieldAccessor;
import net.bytebuddy.implementation.MethodDelegation;
import net.bytebuddy.implementation.SuperMethodCall;
import net.bytebuddy.implementation.bind.annotation.Morph;
import net.bytebuddy.matcher.ElementMatcher;
import net.bytebuddy.matcher.ElementMatchers;
import org.apache.skywalking.apm.agent.core.logging.api.ILog;
import org.apache.skywalking.apm.agent.core.logging.api.LogManager;
import org.apache.skywalking.apm.agent.core.plugin.AbstractClassEnhancePluginDefine;
import org.apache.skywalking.apm.agent.core.plugin.EnhanceContext;
import org.apache.skywalking.apm.agent.core.plugin.PluginException;
import org.apache.skywalking.apm.agent.core.plugin.interceptor.*;
import org.apache.skywalking.apm.util.StringUtil;

import static net.bytebuddy.jar.asm.Opcodes.ACC_PRIVATE;
import static net.bytebuddy.jar.asm.Opcodes.ACC_VOLATILE;
import static net.bytebuddy.matcher.ElementMatchers.isStatic;
import static net.bytebuddy.matcher.ElementMatchers.not;

/**
 * This class controls all enhance operations, including enhance constructors, instance methods and static methods. All
 * the enhances base on three types interceptor point: {@link ConstructorInterceptPoint}, {@link
 * InstanceMethodsInterceptPoint} and {@link StaticMethodsInterceptPoint} If plugin is going to enhance constructors,
 * instance methods, or both, {@link ClassEnhancePluginDefine} will add a field of {@link
 * Object} type.
 *
 * @author wusheng
 *
 * ClassEnhancePluginDefine 抽象类使用了模板方法模式：只定义了增强 static 静态方法、构造方法、实例方法（以及增加字段）的流程，
 * 具体增强哪些方法则由子类实现，也就是说，ClassEnhancePluginDefine 的子类需要实现下面三个方法：
 * 1、getStaticMethodsInterceptPoints()方法：用于获取 static 静态方法增强点，也就是说，指定了增强哪些类的哪些 static 静态方法。
 * 2、getConstructorsInterceptPoints()方法：用于获取构造方法增强点，也就是说，指定增强哪些类的哪些构造方法。
 * 3、getInstanceMethodsInterceptPoints()方法：用于获取实例方法增强点，也就是说，指定增强哪些类的哪些实例方法。
 */
public abstract class ClassEnhancePluginDefine extends AbstractClassEnhancePluginDefine {
    private static final ILog logger = LogManager.getLogger(ClassEnhancePluginDefine.class);

    /**
     * New field name.
     */
    public static final String CONTEXT_ATTR_NAME = "_$EnhancedClassField_ws";

    /**
     * Begin to define how to enhance class.
     * After invoke this method, only means definition is finished.
     *
     * 开始定义如何增强类。
     * 调用此方法后，只意味着定义完成。
     *
     * @param typeDescription target class description
     * @param newClassBuilder byte-buddy's builder to manipulate（操作） class bytecode.
     * @return new byte-buddy's builder for further manipulation.
     */
    // 在 ClassEnhancePluginDefine 实现的 enhance() 方法中，会分别完成对 static 静态方法以及实例方法的增强。
    @Override
    protected DynamicType.Builder<?> enhance(TypeDescription typeDescription,
                                             DynamicType.Builder<?> newClassBuilder,
                                             ClassLoader classLoader,
                                             EnhanceContext context) throws PluginException {
        // 增强static方法
        newClassBuilder = this.enhanceClass(typeDescription, newClassBuilder, classLoader);
        // 增强构造方法和实例方法
        newClassBuilder = this.enhanceInstance(typeDescription, newClassBuilder, classLoader, context);

        return newClassBuilder;
    }

    /**
     * Enhance a class to intercept constructors and class instance methods.
     *
     * @param typeDescription target class description
     * @param newClassBuilder byte-buddy's builder to manipulate class bytecode.
     * @return new byte-buddy's builder for further manipulation.
     */
    private DynamicType.Builder<?> enhanceInstance(TypeDescription typeDescription,
                                                   DynamicType.Builder<?> newClassBuilder,
                                                   ClassLoader classLoader,
                                                   EnhanceContext context) throws PluginException {

        // 只要有 拦截构造方法 或 实例方法，就会执行增强：添加 _$EnhancedClassField_ws 字段，实现 EnhancedInstance 接口。

        ConstructorInterceptPoint[] constructorInterceptPoints = getConstructorsInterceptPoints();
        InstanceMethodsInterceptPoint[] instanceMethodsInterceptPoints = getInstanceMethodsInterceptPoints();
        String enhanceOriginClassName = typeDescription.getTypeName();
        boolean existedConstructorInterceptPoint = false;
        if (constructorInterceptPoints != null && constructorInterceptPoints.length > 0) {
            existedConstructorInterceptPoint = true;
        }
        boolean existedMethodsInterceptPoints = false;
        if (instanceMethodsInterceptPoints != null && instanceMethodsInterceptPoints.length > 0) {
            existedMethodsInterceptPoints = true;
        }

        /**
         * nothing need to be enhanced in class instance, maybe need enhance static methods.
         */
        if (!existedConstructorInterceptPoint && !existedMethodsInterceptPoints) {
            return newClassBuilder;
        }

        /**
         * Manipulate class source code.<br/>
         *
         * new class need:<br/>
         * 1.Add field, name {@link #CONTEXT_ATTR_NAME}.
         * 2.Add a field accessor for this field.
         *
         * And make sure the source codes manipulation only occurs once.（确保以上操作只发生一次）
         *
         */
        // EnhanceContext 记录了整个增强过程中的上下文信息，里面就两个boolean值。
        if (!context.isObjectExtended()) {
            newClassBuilder = newClassBuilder
                    // 为目标类添加了一个字段：private volatile Object _$EnhancedClassField_ws
                    .defineField(CONTEXT_ATTR_NAME, Object.class, ACC_PRIVATE | ACC_VOLATILE)
                    // 实现 EnhancedInstance 接口，用于读写 _$EnhancedClassField_ws 字段
                    .implement(EnhancedInstance.class)
                    .intercept(FieldAccessor.ofField(CONTEXT_ATTR_NAME));
            // 标记一下上下文信息
            context.extendObjectCompleted();
        }

        /**
         * 2. enhance constructors 增强构造方法
         */
        if (existedConstructorInterceptPoint) {
            for (ConstructorInterceptPoint constructorInterceptPoint : constructorInterceptPoints) {
                newClassBuilder = newClassBuilder
                        .constructor(constructorInterceptPoint.getConstructorMatcher())
                        // 这里对 SuperMethodCall 的使用方式和介绍 Byte Buddy 基础时说的一毛一样
                        .intercept(
                                SuperMethodCall.INSTANCE.andThen(
                                        MethodDelegation
                                                .withDefaultConfiguration()
                                                // 这里没有修改构造方法参数的 OverriderArgs 版本，因为此时的构造方法已经调用完成了
                                                .to(new ConstructorInter(constructorInterceptPoint.getConstructorInterceptor(), classLoader))
                                )
                        );
            }
        }

        /**
         * 3. enhance instance methods 增强实例方法
         */
        if (existedMethodsInterceptPoints) {
            for (InstanceMethodsInterceptPoint instanceMethodsInterceptPoint : instanceMethodsInterceptPoints) {
                String interceptor = instanceMethodsInterceptPoint.getMethodsInterceptor();
                if (StringUtil.isEmpty(interceptor)) {
                    throw new EnhanceException("no InstanceMethodsAroundInterceptor define to enhance class " + enhanceOriginClassName);
                }
                // 目标方法的匹配条件
                ElementMatcher.Junction<MethodDescription> junction = not(isStatic()).and(instanceMethodsInterceptPoint.getMethodsMatcher());
                if (instanceMethodsInterceptPoint instanceof DeclaredInstanceMethodsInterceptPoint) {
                    // 目标方法必须定义在目标类中
                    junction = junction.and(ElementMatchers.<MethodDescription>isDeclaredBy(typeDescription));
                }
                if (instanceMethodsInterceptPoint.isOverrideArgs()) { // 修改方法参数
                    newClassBuilder =
                        newClassBuilder.method(junction) // 匹配目标方法
                            .intercept(
                                MethodDelegation.withDefaultConfiguration()
                                    // 使用@Morph注解之前，需要通过Morph.Binder绑定一下
                                    .withBinders(
                                        Morph.Binder.install(OverrideCallable.class)
                                    )
                                    .to(new InstMethodsInterWithOverrideArgs(interceptor, classLoader))
                            );
                } else {
                    newClassBuilder =
                        newClassBuilder.method(junction)
                            .intercept(
                                MethodDelegation.withDefaultConfiguration()
                                    .to(new InstMethodsInter(interceptor, classLoader)) // 在目标方法前后调用相关的方法
                            );
                }
            }
        }

        return newClassBuilder;
    }

    /**
     * Constructor methods intercept point. See {@link ConstructorInterceptPoint}
     *
     * @return collections of {@link ConstructorInterceptPoint}
     */
    protected abstract ConstructorInterceptPoint[] getConstructorsInterceptPoints();

    /**
     * Instance methods intercept point. See {@link InstanceMethodsInterceptPoint}
     *
     * @return collections of {@link InstanceMethodsInterceptPoint}
     */
    protected abstract InstanceMethodsInterceptPoint[] getInstanceMethodsInterceptPoints();

    /**
     * Enhance a class to intercept class static methods.
     *
     * 增强类以拦截类的静态方法。
     *
     * ClassEnhancePluginDefine 是个典型的模板方法模式的使用场景，其 enhanceClass() 方法只
     * 实现了增强静态方法的基本流程，真正的增强逻辑全部通过 getStaticMethodsInterceptPoints()
     * 抽象方法推迟到子类实现。
     *
     * @param typeDescription target class description
     * @param newClassBuilder byte-buddy's builder to manipulate class bytecode.
     * @return new byte-buddy's builder for further manipulation.
     */
    private DynamicType.Builder<?> enhanceClass(TypeDescription typeDescription,
                                                DynamicType.Builder<?> newClassBuilder,
                                                ClassLoader classLoader) throws PluginException {

        // 在增强静态方法时会使用到 StaticMethodsInterceptPoint 这个接口，它描
        // 述了当前插件要拦截目标类的哪些 static 静态方法，以及委托给哪个类去增强。

        // 获取当前插件的静态方法拦截点，如果该插件不增强静态方法，则该数组为空。
        StaticMethodsInterceptPoint[] staticMethodsInterceptPoints = getStaticMethodsInterceptPoints();
        String enhanceOriginClassName = typeDescription.getTypeName();
        if (staticMethodsInterceptPoints == null || staticMethodsInterceptPoints.length == 0) {
            return newClassBuilder;
        }

        for (StaticMethodsInterceptPoint staticMethodsInterceptPoint : staticMethodsInterceptPoints) {
            // 进行具体增强的Interceptor名称
            String interceptor = staticMethodsInterceptPoint.getMethodsInterceptor();
            if (StringUtil.isEmpty(interceptor)) {
                throw new EnhanceException("no StaticMethodsAroundInterceptor define to enhance class " + enhanceOriginClassName);
            }

            // 在增强过程中，是否要修改参数。
            if (staticMethodsInterceptPoint.isOverrideArgs()) {
                // 前面介绍了 Byte Buddy 用法，这里也是一样的，通过method()方法指定拦截方法的条件。
                newClassBuilder = newClassBuilder
                        .method(isStatic().and(staticMethodsInterceptPoint.getMethodsMatcher()))
                        .intercept(
                            MethodDelegation
                                    .withDefaultConfiguration()
                                    .withBinders(Morph.Binder.install(OverrideCallable.class)) // 要用Morph注解，需要先绑定
                                    .to(new StaticMethodsInterWithOverrideArgs(interceptor))
                        );
            } else {
                // 下面是不需要修改参数的增强
                newClassBuilder = newClassBuilder
                        .method(isStatic().and(staticMethodsInterceptPoint.getMethodsMatcher()))
                        .intercept(
                            MethodDelegation
                                    .withDefaultConfiguration()
                                    .to(new StaticMethodsInter(interceptor))
                        );
            }

        }

        return newClassBuilder;
    }

    /**
     * Static methods intercept point. See {@link StaticMethodsInterceptPoint}
     *
     * @return collections of {@link StaticMethodsInterceptPoint}
     */
    protected abstract StaticMethodsInterceptPoint[] getStaticMethodsInterceptPoints();
}
